﻿// SKNotes - a note taking and gathering tool
// Copyright (C) 2009  Stefan Kueng
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

namespace SKNotes
{
    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Threading;
    using ICSharpCode.SharpZipLib.Zip;
    using SKNotes.Utilities;

    public class BackgroundScanner
    {
        private AutoResetEvent resetEvent = new AutoResetEvent(false);

        public void Signal()
        {
            resetEvent.Set();
        }

        public void DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            worker.WorkerSupportsCancellation = true;

            try
            {
                System.Threading.Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
                while (worker.CancellationPending != true)
                {
                    this.ScanNotebookDir(ref worker);
                    this.IndexNotes(ref worker);
                    resetEvent.WaitOne(5000);
                }
            }
            catch (System.InvalidOperationException ex)
            {
                System.Diagnostics.Debug.Print(ex.Message);
            }
            e.Result = true;
        }

        private void IndexNotes(ref BackgroundWorker bw)
        {
            Lucene.Net.Analysis.Standard.StandardAnalyzer analyzer = new Lucene.Net.Analysis.Standard.StandardAnalyzer();
            Lucene.Net.Index.IndexModifier modifier = null;
            try
            {
                modifier = new Lucene.Net.Index.IndexModifier(NoteList.GetNotebookDirPath(), analyzer, false);
            }
            catch (System.Exception)
            {
                try
                {
                    modifier = new Lucene.Net.Index.IndexModifier(NoteList.GetNotebookDirPath(), analyzer, true);
                }
                catch (System.Exception e)
                {
                    System.Diagnostics.Debug.Print(e.Message);
                }
            }

            Collection<Note> toDelete = new Collection<Note>();
            foreach (Note note in NoteList.Instance.Notes)
            {
                if (bw.CancellationPending == true)
                    break;
                if (!File.Exists(note.Path))
                {
                    // leftover note: was loaded from notes.db but doesn't exist physically anymore
                    // add the note to the list for removing it later
                    toDelete.Add(note);
                }
                modifier.DeleteDocuments(new Lucene.Net.Index.Term("id", note.ID));
                modifier.AddDocument(CreateDoc(note, ref bw));
            }
            foreach (Note note in toDelete)
            {
                if (bw.CancellationPending == true)
                    break;
                NoteList.Instance.DeleteNote(note);
            }
            if (bw.CancellationPending == false)
            {
                modifier.Optimize();
            }
            modifier.Close();
        }

        private void ScanNotebookDir(ref BackgroundWorker bw)
        {
            DirectoryInfo noteTestDir = new DirectoryInfo(NoteList.GetNotebookDirPath());
            foreach (DirectoryInfo notebook in noteTestDir.GetDirectories())
            {
                if (bw.CancellationPending == true)
                    break;
                if ((notebook.Attributes & FileAttributes.Hidden) == 0)
                {
                    if (notebook.Name.Equals(TrashFolder.TrashName, StringComparison.OrdinalIgnoreCase))
                    {
                        TrashFolder trashFolder = null;
                        foreach (NoteBook book in NoteList.Instance.Tags)
                        {
                            if (book.GetType() == typeof(TrashFolder))
                            {
                                trashFolder = book as TrashFolder;
                                break;
                            }
                        }

                        if (trashFolder == null)
                        {
                            trashFolder = new TrashFolder();
                            NoteList.Instance.Tags.Add(trashFolder);
                        }

                        foreach (FileInfo noteFile in notebook.GetFiles("*.skn"))
                        {
                            if (bw.CancellationPending == true)
                                break;
                            Note found = NoteList.Instance.Notes.FirstOrDefault((obj) => ((Note)obj).Path == noteFile.FullName);
                            if (found == null)
                            {
                                Note n = new Note(noteFile.FullName, trashFolder);
                                NoteList.Instance.AddNote(n);
                            }
                        }
                    }
                    else
                    {
                        NoteBook noteBook = null;
                        foreach (NoteBook book in NoteList.Instance.Tags)
                        {
                            if (book.NoteBookName == notebook.Name)
                            {
                                noteBook = book;
                                break;
                            }
                        }

                        if (noteBook == null)
                        {
                            noteBook = new NoteBook(notebook.Name);

                            NoteList.Instance.Tags.Add(noteBook);
                            if (NoteList.Instance.CurrentNotebook == null)
                            {
                                NoteList.Instance.CurrentNotebook = noteBook;
                            }
                        }

                        // add new note to search index
                        Lucene.Net.Analysis.Standard.StandardAnalyzer analyzer = new Lucene.Net.Analysis.Standard.StandardAnalyzer();
                        Lucene.Net.Index.IndexModifier modifier = null;
                        try
                        {
                            modifier = new Lucene.Net.Index.IndexModifier(NoteList.GetNotebookDirPath(), analyzer, false);
                        }
                        catch (System.Exception)
                        {
                            try
                            {
                                modifier = new Lucene.Net.Index.IndexModifier(NoteList.GetNotebookDirPath(), analyzer, true);
                            }
                            catch (System.Exception e)
                            {
                                System.Diagnostics.Debug.Print(e.Message);
                            }
                        }

                        foreach (FileInfo noteFile in notebook.GetFiles("*.skn"))
                        {
                            if (bw.CancellationPending == true)
                                break;
                            Note found = NoteList.Instance.Notes.FirstOrDefault((obj) => ((Note)obj).Path == noteFile.FullName);
                            if (found == null)
                            {
                                Note n = new Note(noteFile.FullName, noteBook);
                                noteBook.AddTags(n.Tags);
                                NoteList.Instance.AddNote(n);

                                modifier.DeleteDocuments(new Lucene.Net.Index.Term("id", n.ID));
                                Lucene.Net.Documents.Document doc = CreateDoc(n, ref bw);
                                if (bw.CancellationPending == true)
                                    break;
                                modifier.AddDocument(doc);
                            }
                        }

                        modifier.Close();
                    }
                }
            }
        }

        static Lucene.Net.Documents.Document CreateDoc(Note note, ref BackgroundWorker bw)
        {
            Lucene.Net.Documents.Document doc = new Lucene.Net.Documents.Document();

            doc.Add(new Lucene.Net.Documents.Field("id", note.ID,
                Lucene.Net.Documents.Field.Store.YES,
                Lucene.Net.Documents.Field.Index.UN_TOKENIZED));

            if (bw.CancellationPending == true)
                return doc;

            NoteList.WalkNoteTags(note.Tags, (obj) =>
            {
                doc.Add(new Lucene.Net.Documents.Field("tag", ((NoteTag)obj).TagPath,
                    Lucene.Net.Documents.Field.Store.YES,
                    Lucene.Net.Documents.Field.Index.UN_TOKENIZED));
                return false;
            });

            if (bw.CancellationPending == true)
                return doc;

            doc.Add(new Lucene.Net.Documents.Field("title", new System.IO.StringReader(note.Name)));

            string content = SKNotes.Utilities.HTMLHelpers.HTML2Text(note.Document);
            if (!string.IsNullOrEmpty(content))
                doc.Add(new Lucene.Net.Documents.Field("content", new System.IO.StringReader(content)));

            if (bw.CancellationPending == true)
                return doc;
            // scan the inline files (mostly images)
            foreach (string fileUrl in note.InlineFiles)
            {
                if (bw.CancellationPending == true)
                    break;
                // save the inline file to a temporary location
                string host = fileUrl.Substring(7);
                host = host.Substring(0, host.IndexOf(".skn") + 4);
                // get the file from the note itself
                string zipFilePath = note.NoteBook.Path + "\\" + host;
                string segment = fileUrl.Substring(7);
                segment = segment.Substring(segment.IndexOf(".skn") + 5);
                if (File.Exists(zipFilePath))
                {
                    TempFile tempFile = new TempFile(fileUrl);
                    FileStream output = new FileStream(tempFile.Path, FileMode.Create);
                    using (ZipInputStream s = new ZipInputStream(File.OpenRead(zipFilePath)))
                    {
                        ZipEntry theEntry;
                        while ((theEntry = s.GetNextEntry()) != null)
                        {
                            if ((theEntry.IsFile) && (theEntry.Name == "inlines/" + segment))
                            {
                                // send the data
                                byte[] data = new byte[4096];
                                int size = s.Read(data, 0, data.Length);
                                while (size > 0)
                                {
                                    output.Write(data, 0, size);
                                    size = s.Read(data, 0, data.Length);
                                }
                                break;
                            }
                        }
                        s.Close();
                    }
                    output.Close();

                    try
                    {
                        if (bw.CancellationPending == true)
                            break;

                        TextReader reader = new FilterReader(tempFile.Path);
                        using (reader)
                        {
                            string s = reader.ReadToEnd();
                            doc.Add(new Lucene.Net.Documents.Field("inline", new System.IO.StringReader(s)));
                        }
                    }
                    catch (System.ArgumentException)
                    {

                    }
                    catch (System.Exception e)
                    {
                        System.Diagnostics.Debug.Print(e.Message);
                    }
                }
            }

            // scan the attachment files
            foreach (string fileUrl in note.Attachments)
            {
                if (bw.CancellationPending == true)
                    break;
                // save the inline file to a temporary location
                // get the file from the note itself
                if (File.Exists(note.Path))
                {
                    TempFile tempFile = new TempFile(fileUrl);
                    FileStream output = new FileStream(tempFile.Path, FileMode.Create);
                    using (ZipInputStream s = new ZipInputStream(File.OpenRead(note.Path)))
                    {
                        ZipEntry theEntry;
                        while ((theEntry = s.GetNextEntry()) != null)
                        {
                            if ((theEntry.IsFile) && (theEntry.Name == "attachments/" + fileUrl))
                            {
                                // send the data
                                byte[] data = new byte[4096];
                                int size = s.Read(data, 0, data.Length);
                                while (size > 0)
                                {
                                    output.Write(data, 0, size);
                                    size = s.Read(data, 0, data.Length);
                                }
                                break;
                            }
                        }
                        s.Close();
                    }
                    output.Close();

                    try
                    {
                        if (bw.CancellationPending == true)
                            break;
                        TextReader reader = new FilterReader(tempFile.Path);
                        using (reader)
                        {
                            string s = reader.ReadToEnd();
                            doc.Add(new Lucene.Net.Documents.Field("attachment", new System.IO.StringReader(s)));
                        }
                    }
                    catch (System.ArgumentException)
                    {

                    }
                }
            }

            return doc;
        }
    }
}
